// Copyright 2018 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: Unified Transport Abstraction Layer Unified Transport Abstraction Layer
// feature: discovery support client and server
// feature: log namespace change
// feature: support multiple sending based on topic
// ------------------------------------------------------------------

#include <TCPv4Transport.h>

#include <utility>
#include <cstring>
#include <algorithm>

#include <asio.hpp>
#include <elog/Log.hpp>
#include <transport/TCPv4TransportDescriptor.h>
#include <deps/common/IPLocator.h>
#include <transport/TransportFactory.hpp>
#include <ertps/utils/LocatorAlias.h>
using namespace std;
using namespace asio;

namespace vbs {
namespace transport {

using IPFinder = vbsutil::xmlparser::IPFinder;
using octet = ertps::rtps::octet;
using IPLocator = ertps::rtps::IPLocator;
using Log = vbsutil::elog::Log;

static void get_ipv4s(std::vector<IPFinder::info_IP>& locNames, bool return_loopback = false) {
    (void)IPFinder::getIPs(&locNames, return_loopback);
    auto new_end = remove_if(locNames.begin(), locNames.end(), [](IPFinder::info_IP ip) {
        return ((ip.type != IPFinder::IP4) && (ip.type != IPFinder::IP4_LOCAL));
    });
    (void)locNames.erase(new_end, locNames.end());
    (void)std::for_each(locNames.begin(), locNames.end(),
                        [](IPFinder::info_IP& loc) { loc.locator.kind = LOCATOR_KIND_TCPv4; });
}

static asio::ip::address_v4::bytes_type Locator_to_native(Locator& locator, const octet* local_wan) {
    const octet* wan = IPLocator::getWan(locator);
    if (IPLocator::hasWan(locator) && (memcmp(local_wan, wan, 4) != 0)) {
        return {{wan[0], wan[1], wan[2], wan[3]}};
    } else {
        return {{IPLocator::getIPv4(locator)[0], IPLocator::getIPv4(locator)[1], IPLocator::getIPv4(locator)[2],
                 IPLocator::getIPv4(locator)[3]}};
    }
}

TCPv4Transport::TCPv4Transport(const TCPv4TransportDescriptor& descriptor)
    : TCPTransportInterface(LOCATOR_KIND_TCPv4), configuration_(descriptor) {
    for (const auto& interface : descriptor.interfaceWhiteList) {
        interface_whitelist_.emplace_back(interface);
    }

    // for (uint16_t port : configuration_.listening_ports) {
    //     Locator locator(LOCATOR_KIND_TCPv4, port);
    //     (void)create_acceptor_socket(locator);
    // }
}

TCPv4Transport::TCPv4Transport() : TCPTransportInterface(LOCATOR_KIND_TCPv4) {}

TCPv4Transport::~TCPv4Transport() {
    clean();
}

TCPv4TransportDescriptor::TCPv4TransportDescriptor() : TCPTransportDescriptor() {
    (void)memset(wan_addr, 0, 4);
}

TCPv4TransportDescriptor::TCPv4TransportDescriptor(const TCPv4TransportDescriptor& t)
    : TCPTransportDescriptor(static_cast<const TCPTransportDescriptor&>(t)) {
    (void)memcpy(wan_addr, t.wan_addr, 4);
}

TCPv4TransportDescriptor& TCPv4TransportDescriptor::operator=(const TCPv4TransportDescriptor& t) {
    if (this != &t) {
        TCPTransportDescriptor::operator=(static_cast<const TCPTransportDescriptor&>(t));
        (void)memcpy(wan_addr, t.wan_addr, 4);
    }

    return *this;
}

bool TCPv4TransportDescriptor::operator==(const TCPv4TransportDescriptor& t) const {
    return ((this->wan_addr[0] == t.wan_addr[0]) && (this->wan_addr[1] == t.wan_addr[1]) &&
            (this->wan_addr[2] == t.wan_addr[2]) && (this->wan_addr[3] == t.wan_addr[3]) &&
            TCPTransportDescriptor::operator==(t));
}

TransportInterface* TCPv4TransportDescriptor::create_transport() const {
    try {
        return new TCPv4Transport(*this);
    } catch (const asio::invalid_service_owner& e) {
        elogError(IP_LOCATOR, RetCode_t::RETCODE_BAD_PARAMETER,
                  "new TCPv4Transport(*this) catch invalid_service_owner: " << e.what());
        return nullptr;
    } catch (const asio::system_error& e) {
        elogError(IP_LOCATOR, RetCode_t::RETCODE_ERROR, "new TCPv4Transport(*this) catch system_error: " << e.what());
        return nullptr;
    } catch (const asio::service_already_exists& e) {
        elogError(IP_LOCATOR, RetCode_t::RETCODE_PRECONDITION_NOT_MET,
                  "new TCPv4Transport(*this) catch asio::service_already_exists: " << e.what());
        return nullptr;
    } catch (const std::exception& e) {
        elogError(IP_LOCATOR, RetCode_t::RETCODE_ERROR, "new TCPv4Transport(*this) catch other error: " << e.what());
        return nullptr;
    }
}

TransmitorType TCPv4TransportDescriptor::get_type() const {
    return TransmitorType::TRANSMITOR_TYPE_TCP;
}

void TCPv4Transport::AddDefaultOutputLocator(LocatorList&) {}

const TCPTransportDescriptor* TCPv4Transport::configuration() const {
    return &configuration_;
}

TCPTransportDescriptor* TCPv4Transport::configuration() {
    return &configuration_;
}

void TCPv4Transport::get_ips(std::vector<IPFinder::info_IP>& locNames, bool return_loopback) const {
    get_ipv4s(locNames, return_loopback);
}

bool TCPv4Transport::is_locator_allowed(const Locator& locator) const {
    (void)locator;
    return false;
}

LocatorList TCPv4Transport::NormalizeLocator(const Locator& locator) {
    LocatorList list;

    list.push_back(locator);

    return list;
}

bool TCPv4Transport::is_local_locator(const Locator& locator) const {
    assert(locator.kind == LOCATOR_KIND_TCPv4);

    /*
     * Check case: Remote WAN address isn't our WAN address.
     */
    if (IPLocator::hasWan(locator)) {
        const octet* wan = IPLocator::getWan(locator);
        if (memcmp(wan, configuration_.wan_addr, 4 * sizeof(octet)) != 0) {
            return false;  // WAN mismatch
        }
    }

    /*
     * Check case: Address is localhost
     */
    if (IPLocator::isLocal(locator)) {
        return true;
    }

    /*
     * Check case: Address is one of our addresses.
     */
    for (const IPFinder::info_IP& localInterface : current_interfaces_) {
        if (IPLocator::compareAddress(locator, localInterface.locator)) {
            return true;
        }
    }

    return false;
}

bool TCPv4Transport::compare_locator_ip(const Locator& lh, const Locator& rh) const {
    return IPLocator::compareAddress(lh, rh);
}

bool TCPv4Transport::compare_locator_ip_and_port(const Locator& lh, const Locator& rh) const {
    return IPLocator::compareAddressAndPhysicalPort(lh, rh);
}

void TCPv4Transport::fill_local_ip(Locator& loc) const {
    loc.kind = kind();
    (void)IPLocator::setIPv4(loc, "127.0.0.1");
}

ip::tcp::endpoint TCPv4Transport::generate_endpoint(const Locator& loc, uint16_t port) const {
    asio::ip::address_v4::bytes_type remoteAddress;
    (void)IPLocator::copyIPv4(loc, remoteAddress.data());
    return ip::tcp::endpoint(asio::ip::address_v4(remoteAddress), port);
}

ip::tcp::endpoint TCPv4Transport::generate_local_endpoint(Locator& loc, uint16_t port) const {
    return ip::tcp::endpoint(asio::ip::address_v4(Locator_to_native(loc, configuration_.wan_addr)), port);
}

ip::tcp::endpoint TCPv4Transport::generate_endpoint(uint16_t port) const {
    return asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port);
}

asio::ip::tcp TCPv4Transport::generate_protocol() const {
    return asio::ip::tcp::v4();
}
void TCPv4Transport::set_receive_buffer_size(uint32_t size) {
    configuration_.receiveBufferSize = size;
}

void TCPv4Transport::set_send_buffer_size(uint32_t size) {
    configuration_.sendBufferSize = size;
}

void TCPv4Transport::endpoint_to_locator(const ip::tcp::endpoint& endpoint, Locator& locator) const {
    locator.kind = LOCATOR_KIND_TCPv4;
    (void)IPLocator::setPhysicalPort(locator, endpoint.port());
    auto ipBytes = endpoint.address().to_v4().to_bytes();
    (void)IPLocator::setIPv4(locator, ipBytes.data());
}

bool TCPv4Transport::fillMetatrafficUnicastLocator(Locator& locator, uint32_t metatraffic_unicast_port) const {
    (void)locator;
    (void)metatraffic_unicast_port;
    return false;
}

bool TCPv4Transport::fillUnicastLocator(Locator& locator, uint32_t well_known_port) const {
    (void)locator;
    (void)well_known_port;
    return false;
}
}  // namespace transport
}  // namespace vbs
